import chardet
import time
import arrow
import socket
import traceback
from io import StringIO
import paramiko
from time import sleep, time as ticknow
from inspect import isfunction

def p_debug(debug, print_str):
    if debug:
        print(print_str)

def print_content_summary(content, title='', count=10):
    count_half = int(float(count)/2)
    content_split = content.split('\n')
    conent_len = len(content_split)
    print_list = ['{}{}{}'.format('-'*70, title, '-'*(70-len(str(title))))]
    line_prefix = '    |{}'
    if conent_len <= count:
        for line in content_split:
            print_list.append(line_prefix.format(line))
    else:
        for line in content_split[:count_half]:
            print_list.append(line_prefix.format(line))
        skip_str = '{}{}行数：{}，超过：{}，中间行省略{}'.format(' '*30, '.'*20, conent_len, count, '.'*20)
        print_list.append(line_prefix.format(skip_str))
        for line in content_split[-1*count_half:]:
            print_list.append(line_prefix.format(line))
    print_list.append('=' * 140)
    print('\n'.join(print_list))

def seconds2dhms(total_seconds):
    if not (isinstance(total_seconds, float) or isinstance(total_seconds, int)):
        return ''
    s, ms = divmod(total_seconds*1000, 1000)
    m, s = divmod(int(s), 60)
    h, m = divmod(m, 60)
    d, h = divmod(h, 24)
    if d:
        return '{}d {}h {}m {}s'.format(d, h, m, s)
    if h:
        return '{}h {}m {}s'.format(h, m, s)
    if m:
        return '{}m {}s'.format(m, s)
    if s:
        return '{}s'.format(s)
    if ms:
        return '{}ms'.format(int(ms))
    return '0s'
    
def try_decode_content(config_got):
    if isinstance(config_got, str):
        return config_got
    chardet_out = chardet.detect(config_got)
    config_got_code = chardet_out['encoding']
    if not config_got_code:
        config_got_code = 'utf-8'
    for CodeOne in ['utf-8', 'gb2312', 'GBK', 'gb18030', config_got_code]:
        try:
            config_got = config_got.decode(CodeOne)
        except UnicodeDecodeError:
            pass
        else:
            # print '成功找到编码方式：' + CodeOne
            break
    else:
        # print u'没有找到合适的编码，强制使用utf-8'
        config_got = config_got.decode('utf-8', errors='ignore')
    # print 'convertEncodeToUtf8 after type(config_got)', type(config_got)
    return config_got


def replace_controle_char(command_return, debug=False):
    # if debug:
    #     print('replace_special_char 替换干扰判断的控制字符')
    command_return = command_return.strip().replace('\x00', '').replace('\x08', '').\
        replace('\r', '\n').replace('\n\n', '\n').replace('\n\n', '\n')
    return command_return


def replace_controle_char_v2(content):
    if isinstance(content, str):
        content = content.\
            replace('\x00', '').\
            replace('\x08', '').\
            replace('\r', '')
    elif isinstance(content, bytes):
        content = content.\
            replace(b'\x00', b'').\
            replace(b'\x08', b'').\
            replace(b'\r', b'')
    return content


def replace_prompt_char(prompt):
    if prompt is None:
        return None
    prompt_chars = '<>[]#$'
    for prompt_char in prompt_chars:
        prompt = prompt.replace(prompt_char, '`')
    return prompt


def replace_more_char(config_got, debug=None):
    more_prompt_list = [
        '  ---- More ----\n\x1b[42D                                          \x1b[42D',
        '  ---- More ----\x1b[42D                                          \x1b[42D',
        '---- More ----\x1b[42D                                          \x1b[42D',
        '  ---- More ----\n\x1b[16D                \x1b[16D',
        '  ---- More ----\x1b[16D                \x1b[16D',
        '---- More ----\x1b[16D                \x1b[16D',
        '---- More ----\n\n               \n',
        '---- More ----\n               \n',
        '---- More ----\n               ',
        '---- More ----               ',
        ' --More-- \n          ',
        ' --More--           ',
        '--More-- or (q)uit\n',
        '--More--\x1b[2K',
        ' --More--\n',
        '--More--\n',
        '---MORE---\n',
    ]
    if '--MORE--' in config_got.replace(' ', '').upper():
        for index, moreStr in enumerate(more_prompt_list):
            if moreStr in config_got:
                if debug:
                    print('replace_more_char 发现特征串：index：' + str(index) + ' 内容：' + repr(moreStr))
                config_got = config_got.replace(moreStr, '')
            else:
                # if debug:
                #     print u'这个moreStr 没发现有', repr(moreStr)
                pass
    else:
        if debug:
            print('replace_more_char 没有发现特征串：--More--')
        pass
    return config_got


class LineList:
    line_list = None
    # content_list = None
    # line_str = None
    def __init__(self):
        self.line_list = []
        # self.content_list = []

    def append(self, content):
        # self.content_list.append(content)
        # self.line_str = ''.join(self.content_list)
        new_list = content.split('\n')
        if self.line_list:
            self.line_list[-1] = self.line_list[-1] + new_list.pop(0)
        self.line_list.extend(new_list)

    def print(self):
        # print('\n\n')
        # print('-' * 50)
        # print(self.line_str)
        print('*' * 50)
        print('\n'.join(self.line_list[-5:]))
        print('=' * 50)

    def content(self, start=None):
        if start:
            return '\n'.join(self.line_list[start:])
        else:
            return '\n'.join(self.line_list)

    def count(self):
        return len(self.line_list)

class Timer:

    def __init__(self):
        self._start = time.time()
        self._last = self._start

    def float(self):
        return time.time() - self._start

    def str(self):
        return seconds2dhms(time.time() - self._start)

    def cut(self):
        time_now = time.time()
        cut_str = seconds2dhms(time_now - self._last)
        self._last = time_now
        return cut_str

    def print(self):
        print(seconds2dhms(time.time() - self._start))

def set_prarmiko_log(log_file):
    paramiko.common.logging.basicConfig(level=paramiko.common.DEBUG)
    paramiko.util.log_to_file(log_file)

class SshException(Exception):
    except_str = None
    ssh_debug = None

    def __init__(self, except_str, ssh_debug=None):
        self.except_str = str(except_str)
        self.ssh_debug = str(ssh_debug)
        Exception.__init__(self, self.except_str)

    def __str__(self):
        return self.except_str


def what_to_do_next(content, prompt, except_dict=None, force_action_dict=None, stop_str_list=None, fast=False, option=None, debug=False):
    content = replace_controle_char(try_decode_content(content), debug=debug)
    content_len = len(content.strip())
    content_last_50_repr = repr(content.strip()[-50:])
    content_last_50_repr_reverse = content_last_50_repr[::-1]
    last_line = content.split("\n")[-1:][0].strip()
    last_line_reverse = last_line[::-1]
    if prompt:
        prompt = try_decode_content(prompt)
        prompt_reverse = prompt[::-1]
    else:
        prompt = ''
        prompt_reverse = None
    if isinstance(option, dict) and option.get('replace_last_doller', False):
        if '$' in last_line:
            last_line = last_line.replace('$', '').strip()

    if isinstance(option, dict) and 'what_to_do_next' in option:
        content, state_str, action_str = option['what_to_do_next'](content)
        if state_str:
            return content, state_str, action_str, content_len

    if force_action_dict:
        for except_str, except_action in force_action_dict.items():
            if except_str in content:
                state_str = '遇到自定义的强制交互特征串'
                action_str = except_action
                return content, state_str, action_str, content.find(except_str)
    if except_dict:
        for except_str, except_action in except_dict.items():
            if last_line_reverse.find(except_str[::-1]) == 0:
                state_str = '最后字符等于自定义的交互特征串'
                action_str = except_action
                return content, state_str, action_str, content_len
    if stop_str_list:
        for except_str in stop_str_list:
            if except_str in content:
                state_str = '遇到自定义的中断特征串'
                action_str = None  # 遇到中断特征串，不会执行动作，直接跳出
                return content, state_str, action_str, content.find(except_str)
    if prompt and replace_prompt_char(last_line) == replace_prompt_char(prompt):
        state_str = '最后一行等于提示符'
        action_str = ''
        return content, state_str, action_str, content_len
    if prompt and replace_prompt_char(prompt) in replace_prompt_char(last_line):
        state_str = '最后一行含有提示符'
        action_str = ''
        if replace_prompt_char(last_line.replace('\x1b[1D', '').strip()) == replace_prompt_char(prompt):
            state_str = '最后一行等于提示符'
        return content, state_str, action_str, content_len
    if last_line in ['---- More ----', '--More--', '---MORE---']:
        state_str = '最后一行遇到More'
        if fast:
            action_str = ' ' * 100
        else:
            action_str = ' '
        return content, state_str, action_str, content_len
    if prompt_reverse and content_last_50_repr_reverse.find(prompt_reverse) == 0:
        state_str = '最后字符串遇到提示符'
        action_str = None
        return content, state_str, action_str, content_len

    login_except_dict = {
        'The password needs to be changed. Change now? [Y/N]:': 'N\n',
        'day(s). Would you like to input a new one? [Y/N]:': 'N\n'
    }
    for except_str, except_action in login_except_dict.items():
        if last_line_reverse.find(except_str[::-1]) == 0:
            state_str = '登录遇到改密码提示'
            action_str = except_action
            return content, state_str, action_str, content_len
    quit_except_dict = {
        'Are you sure you want to logout? (y/n): ': 'y\n',
    }
    for except_str, except_action in quit_except_dict.items():
        if last_line_reverse.find(except_str[::-1]) == 0:
            state_str = '退出时遇到确认提示'
            action_str = except_action
            return content, state_str, action_str, content_len
    return content, None, None, content_len


class SshConnect:
    password = None
    key = None
    key_password = None
    use_adminset_key = None
    enable_password = None
    db_prompt = None
    debug = None

    prompt = None
    sysrole = None
    sysname = None
    errorstr = None
    lastout = None
    lastline = None
    action_set = None
    transport = None
    client = None
    channel = None

    def __init__(self, host, prompt=None, port=None, user=None,
                 password=None, key=None, key_password=None, use_adminset_key=False,
                 enable_password=None, debug=None):
        self.host = host
        if port is None:
            self.port = 22
        else:
            self.port = port
        self.user = user
        self.password = password
        self.key = key
        self.key_password = key_password
        self.use_adminset_key = use_adminset_key
        self.enable_password = enable_password
        self.login_except_dict = {}
        self.db_prompt = prompt
        self.debug = debug
        self.option = {}
        self.action_set = set()
        self.client = paramiko.SSHClient()
        self.client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        self.connect()

    def set_option(self, option_key, option_value):
        self.option[option_key] = option_value

    def connect(self):
        try:
            if self.password:
                p_debug(self.debug, 'connect 到主机：{}，使用密码方式'.format(self.host))
                self.client.connect(self.host, self.port, username=self.user, password=self.password,
                                    allow_agent=False, look_for_keys=False, timeout=5)
            elif self.key:
                p_debug(self.debug, 'connect 到主机：{}，使用秘钥方式'.format(self.host))
                self.client.connect(self.host, self.port, username=self.user, key_filename=self.key, password=self.key_password,
                                    allow_agent=False, look_for_keys=False, timeout=5)
            elif self.use_adminset_key:
                key_pass_file = '/var/opt/adminset/data/scripts/update_privilege_monitor/pa.log'
                with open(key_pass_file, 'r') as f:
                    key_pass = f.read().strip()
                key_file = '/root/.ssh/adminset'
                self.user = 'monitor'
                p_debug(self.debug, 'connect 到主机：{}，使用Adminset秘钥'.format(self.host))
                self.client.connect(self.host, self.port, username=self.user, key_filename=key_file, password=key_pass,
                                    allow_agent=False, look_for_keys=False, timeout=5)
            else:
                p_debug(self.debug, '异常，password 和 key 不能同时为空')
                self.errorstr = 'password 和 key 不能同时为空'
                return
            self.transport = self.client.get_transport()
            self.channel = self.client.invoke_shell(term='vt100', width=1024, height=1024)
            self.channel.settimeout(5)
        except socket.timeout:
            p_debug(self.debug, '连接到设备失败，网络不通')
            self.errorstr = '到设备的ssh 端口不通'
            return
        except paramiko.ssh_exception.NoValidConnectionsError as e:
            p_debug(self.debug, '登录到设备ssh 失败：{} 信息：{}'.format(type(e), e))
            p_debug(self.debug, '连接到设备失败，连接异常')
            self.errorstr = '到设备的ssh 连接异常'
            return
        except paramiko.ssh_exception.BadAuthenticationType as e:
            p_debug(self.debug, '登录到设备ssh 失败：{} 信息：{}'.format(type(e), e))
            p_debug(self.debug, '连接到设备失败，验证鉴权方式不匹配')
            self.errorstr = '验证鉴权方式不匹配'
            return
        except paramiko.ssh_exception.AuthenticationException as e:
            p_debug(self.debug, '登录到设备ssh 失败：{} 信息：{}'.format(type(e), e))
            p_debug(self.debug, '连接到设备失败，验证鉴权异常')
            self.errorstr = '验证鉴权异常'
            return
        except Exception as e:
            p_debug(self.debug, '登录到设备ssh 失败：{} 信息：{}'.format(type(e), e))
            p_debug(self.debug, traceback.format_exc())
            self.errorstr = str(e)
            return
        p_debug(self.debug, 'ssh 连接建立成功')

        recv_str = self.get_and_send(fast=False, prompt=self.db_prompt, except_dict=None)

        self.prompt = recv_str.split("\n")[-1:][0].replace('\x00', '')
        if len(self.prompt) == 0:
            self.prompt = None
        if self.prompt is None:
            return

        prompt_split = replace_prompt_char(self.prompt).split('`')
        if len(prompt_split) == 3:
            self.sysrole = prompt_split[0]
            self.sysname = prompt_split[1]
        elif len(prompt_split) == 2:
            self.sysrole = ''
            self.sysname = prompt_split[0]

        p_debug(self.debug, '连接到设备完成，提示符：{}'.format(self.prompt))
        if self.db_prompt and replace_prompt_char(self.prompt) != replace_prompt_char(self.db_prompt):
            print('设备命令提示符发生改变：' + str(self.db_prompt) + ' =======>>>>>>> ' + str(self.prompt))

        if self.enable_password is None:
            p_debug(self.debug, 'password_enable 不存在，连接完成，提示符：{}'.format(self.prompt))
            return
        p_debug(self.debug, '存在 password_enable，进入enable模式')
        self.channel.send('enable\n')

        except_dict = {
            'Password:': self.enable_password + '\n',
            'password:': self.enable_password + '\n'
        }
        recv_str = self.get_and_send(fast=False, prompt=self.prompt, except_dict=except_dict)

        prompt_in_enable = recv_str.split("\n")[-1:][0].replace('\x00', '')
        if 'password' in prompt_in_enable.lower():
            print('异常，enable再次提示输入密码，似乎enable失败！')
            self.errorstr = 'enable模式进入失败'
            return

        if self.prompt == prompt_in_enable:
            if prompt_in_enable[-1] == '#':
                print('注意，enable模式提示符和普通模式一样，末尾为#，断定为无须enable！')
            else:
                print('异常，enable模式提示符和普通模式应该不一样，这可能是enable失败！')
                self.errorstr = 'enable模式进入失败'
                return

        self.prompt = recv_str.split("\n")[-1:][0].replace('\x00', '')
        p_debug(self.debug, '进入enable 模式完成，提示符：{}'.format(self.prompt))

    def set_prompt(self, prompt):
        self.prompt = prompt

    def get_transport(self):
        return self.client.get_transport()

    def check_transport(self):
        if self.transport is None:
            return False
        return self.transport.is_active()

    def check_channel(self):
        return not self.channel.exit_status_ready()

    def excute(self, command, stop_only_on_prompt=False, fast=False, except_dict=None, force_action_dict=None, stop_str_list=None):
        if self.errorstr:
            p_debug(self.debug, 'errorstr 不为空，不能执行命令，{}'.format(self.errorstr))
            raise SshException(self.host + ' ' + str(self.prompt) + ' ' + str(self.errorstr))
        p_debug(self.debug, '\n\n')
        p_debug(self.debug, '准备执行命令：{}'.format(repr(command)))
        p_debug(self.debug, '是否fast模式：{}'.format(fast))

        if len(command + '\n') > 2040:
            p_debug(self.debug, '命令太长，将被拆分成最大2040长度的列表，逐段send')
            part = 0
            command = command + '\n'
            while True:
                start = part * 2040
                end = start + 2040
                part += 1
                command_part = command[start:end]
                if not command_part:
                    break
                p_debug(self.debug, '{}\n{}'.format(
                    '-' * 100, '分段长度：{}'.format(len(command_part))
                ))
                if self.debug:
                    print_content_summary(command_part)
                self.channel.send(command_part)
                sleep(0.1)
        else:
            self.channel.send(command + '\n')
        recv_str = self.get_and_send(fast=fast, prompt=self.prompt,
                                     stop_only_on_prompt=stop_only_on_prompt, except_dict=except_dict, force_action_dict=force_action_dict, stop_str_list=stop_str_list)
        return recv_str

    def get_and_send(self, fast=False, prompt=None, stop_only_on_prompt=False, except_dict=None, force_action_dict=None, stop_str_list=None):
        p_debug(self.debug, '\n')
        if not self.check_transport():
            p_debug(self.debug, 'transport_isalive 检查发现：{} {} 通道不在存活状态'.format(self.host, self.prompt))
            raise SshException('{} {} 通道不在存活状态'.format(self.host, self.prompt))
        if prompt is None:
            prompt = self.prompt
        if except_dict is None:
            except_dict = {}
        total_timeout = 60 * 30
        idle_timeout = 60 * 5
        p_debug(self.debug, '{}\n{}\n{}\n{}\n{}\n{}\n{}'.format(
            'get_and_send start {}'.format('-' * 50),
            'idle_timeout：{}'.format(idle_timeout),
            'fast：{}'.format(fast),
            'prompt：{}'.format(prompt),
            'stop_only_on_prompt：{}'.format(stop_only_on_prompt),
            'except_dict：{}'.format(except_dict),
            'stop_str_list：{}'.format(stop_str_list),
        ))

        recv_list = []
        recv_str = ''
        if fast:
            recv_len = 10240
            self.channel.settimeout(0.5)
            self.channel.send(' ' * 5)
        else:
            recv_len = 4096
            self.channel.settimeout(5)
        idle_starttime = arrow.now()  # 监测ssh 无交互的时间，无交互表示：既不是提示符，也不是强制交互串，也不是其他既定的模式，进入死循环
        total_starttime = arrow.now()
        recv_count_last = -1
        while True:
            start = ticknow()
            try:
                sleep(0.05)
                if fast:
                    try:
                        while True: recv_list.append(self.channel.recv(recv_len))
                    except socket.timeout: pass
                else:
                    recv_list.append(self.channel.recv(recv_len))
                recv_str = b''.join(recv_list)
                recv_count = len(recv_list)
                if recv_count == recv_count_last:
                    p_debug(self.debug, '异常，缓冲区没有新内容')
                else:
                    idle_starttime = arrow.now()
                    p_debug(self.debug, '收到内容块数：块数：{}，最后120个字符：{}'.format(
                        recv_count, repr(recv_str.strip()[-120:])))
                recv_count_last = recv_count
                recv_str, state_str, action_str, content_len = what_to_do_next(
                    content=recv_str, prompt=prompt, except_dict=except_dict, force_action_dict=force_action_dict,
                    stop_str_list=stop_str_list, fast=fast, option=self.option, debug=self.debug)
                if self.debug and state_str and not (fast and '遇到More' in state_str):
                    print('分析出状态：' + state_str + '，动作：' + repr(action_str))
                if isfunction(action_str):
                    p_debug(self.debug, '执行回调函数：{}'.format(action_str.__name__))
                    action_str(recv_str)
                    recv_list = []
                    idle_starttime = arrow.now()
                    total_starttime = arrow.now()
                    p_debug(self.debug, '执行回调函数后，继续接收')
                    continue
                if action_str:
                    self.channel.send(action_str)
                if state_str and '提示符' in state_str:
                    break
                if state_str and '遇到自定义的中断特征串' in state_str:
                    break
                if state_str and not action_str:
                    break
            except socket.timeout:
                if stop_only_on_prompt or stop_str_list:
                    p_debug(self.debug, '慢速模式接收超时，需等待提示符或自定义中断字符')
                else:
                    command_return, state_str, action_str, content_len = what_to_do_next(
                        content=recv_str, prompt=prompt, except_dict=except_dict, force_action_dict=force_action_dict, stop_str_list=stop_str_list, option=self.option)
                    if state_str:
                        p_debug(self.debug, '分析出状态：{}，动作：{}，因异常未执行'.format(state_str, repr(action_str)))
                    p_debug(self.debug, '未设置强制等待提示符或自定义中断字符，将中断接收')
                    break
            except Exception as e:
                print('SshConnect get_and_send 发生未知异常：{}，信息：{}'.format(type(e), e))
                print(traceback.format_exc())
                command_return, state_str, action_str, content_len = what_to_do_next(
                    content=recv_str, prompt=prompt, except_dict=except_dict, force_action_dict=force_action_dict, stop_str_list=stop_str_list, option=self.option)
                if state_str:
                    p_debug(self.debug, '分析出状态：{}，动作：{}，因发生未知异常未执行'.format(state_str, repr(action_str)))
                self.errorstr = str(e)
                raise SshException('主机：{}，提示符：{}，发生未知异常：{}'.format(self.host, prompt, e))
            if not self.check_channel():
                p_debug(self.debug, 'ssh终端不在存活状态')
                break
            total_diff_s = (arrow.now() - total_starttime).total_seconds()
            if total_diff_s > total_timeout:
                qq_message = 'ssh命令执行总时间超过阈值({}s)，当前值：{}s，主机：{}，提示符：{}，最后5行内容：\n{}'.format(
                    total_timeout, int(total_diff_s), self.host, prompt, '\n'.join(recv_str.split('\n')[-5:])
                )
                print(qq_message)
                raise SshException(self.host + ' ' + str(prompt) + ' ssh命令执行总时间超过阈值：' + str(total_timeout))
            idle_diff_s = (arrow.now() - idle_starttime).total_seconds()
            if idle_diff_s > idle_timeout:
                qq_message = 'ssh无交互时间超过阈值({}s)，当前值：{}s，主机：{}，提示符：{}，最后5行内容：\n{}'.format(
                    idle_timeout, int(idle_diff_s), self.host, prompt, '\n'.join(recv_str.split('\n')[-5:])
                )
                print(qq_message)
                raise SshException(self.host + ' ' + str(prompt) + ' ssh idle 时间超过阈值：' + str(idle_timeout))
            p_debug(self.debug, 'get_and_send 外层 while 结束，耗时：{}'.format(ticknow() - start))
        if fast:
            total_diff_s = (arrow.now() - total_starttime).total_seconds()
            cmd_speed = float(len(recv_str)) / total_diff_s
            p_debug(self.debug, 'get_and_send 耗时：{}'.format(total_diff_s))
            p_debug(self.debug, 'get_and_send 每秒字符数：{}'.format(int(cmd_speed)))
        if self.debug:
            print_content_summary(recv_str, count=10)
        self.lastout = '\n'.join(recv_str.split('\n')[-5:])
        self.lastline = '\n'.join(recv_str.split('\n')[-1:])
        return recv_str

    def clear(self):
        if self.transport:
            self.transport.close()
            self.transport = None
        if self.client:
            self.client.close()
            self.client = None
        self.channel = None


class WhatToDoNext:
    fast = None
    debug = None
    except_dict = None
    force_action_dict = None
    stop_str_list = None
    prompt = None
    prompt_reverse = None

    replace_last_doller = None
    custom_parser = None
    buffer = None
    buffer_len = None
    buffer_raw = None
    buffer_raw_len = None
    buffer_fast = None
    has_buffer = False
    line_list_obj = None
    total_line_list = None
    total_line_list_start = None
    line_list_len = None
    line_list_newin = None
    content_raw = None
    content_raw_len = None
    content = None
    content_len = None
    content_len_last = None
    content_newin = None
    content_last_50_repr = None
    content_last_50_repr_reverse = None
    last_line = None
    last_line_reverse = None
    idle_tick = None
    total_tick = None
    idle_timeout = None
    total_timeout = None
    action_pos = None
    action_line = None

    def __init__(self, host, fast=False, debug=None):
        self.host = host
        self.fast = fast
        self.debug = debug
        self.buffer = []
        self.buffer_len = 0
        self.buffer_raw = []
        self.buffer_raw_len = 0
        self.buffer_fast = []
        self.line_list_obj = LineList()
        self.line_list_len = self.line_list_obj.count()
        self.idle_tick = ticknow()
        self.total_tick = ticknow()
        self.idle_timeout = 1*60
        self.total_timeout = 20*60
        self.action_pos = 0
        self.action_line = 0

    def set_idle_timeout(self, idle_timeout):
        self.idle_timeout = idle_timeout

    def set_total_timeout(self, total_timeout):
        self.total_timeout = total_timeout

    def set_total_line_list(self, total_line_list):
        self.total_line_list = total_line_list
        self.total_line_list_start = len(total_line_list.line_list) - 1

    def set_replace_last_doller(self, replace_last_doller):
        self.replace_last_doller = replace_last_doller

    def set_custom_parser(self, custom_parser):
        self.custom_parser = custom_parser

    def set_except_dict(self, except_dict):
        self.except_dict = except_dict

    def set_force_action_dict(self, force_action_dict):
        self.force_action_dict = force_action_dict

    def set_stop_str_list(self, stop_str_list):
        self.stop_str_list = stop_str_list

    def set_prompt(self, prompt):
        self.prompt = prompt
        if prompt:
            self.prompt = try_decode_content(prompt)
            self.prompt_reverse = prompt[::-1]
        else:
            self.prompt = ''
            self.prompt_reverse = None

    def fast_append(self, recv_raw):
        self.buffer_fast.append(recv_raw)

    def merge_fast_buffer_to_buffer(self):
        if self.buffer_fast:
            recv_raw = b''.join(self.buffer_fast)
            self.buffer_raw.append(recv_raw)
            recv_str = replace_controle_char_v2(try_decode_content(recv_raw))
            self.buffer.append(recv_str)
            self.line_list_obj.append(recv_str)
            self.line_list_len = self.line_list_obj.count()
            if self.total_line_list:
                self.total_line_list.append(recv_str)
            self.buffer_fast.clear()

    def append(self, recv_raw):
        self.merge_fast_buffer_to_buffer()
        self.buffer_raw.append(recv_raw)
        recv_str = replace_controle_char_v2(try_decode_content(recv_raw))
        self.buffer.append(recv_str)
        self.line_list_obj.append(recv_str)
        self.line_list_len = self.line_list_obj.count()
        if self.total_line_list:
            self.total_line_list.append(recv_str)

    def check_idle(self):
        idle_diff_s = ticknow() - self.idle_tick
        total_diff_s = ticknow() - self.total_tick
        is_idle_timeout = False
        is_total_timeout = False
        if idle_diff_s > self.idle_timeout:
            if not self.content:
                self.content = ''
            message = 'ssh无交互时间超过阈值({}s)，当前值：{}s，主机：{}，提示符：{}，最后5行内容：\n{}'.format(
                self.idle_timeout, int(idle_diff_s), self.host, self.prompt, '\n'.join(self.content.strip().split('\n')[-5:])
            )
            print(message)
            is_idle_timeout = True
        if total_diff_s > self.total_timeout:
            if not self.content:
                self.content = ''
            message = 'ssh总运行时间超过阈值({}s)，当前值：{}s，主机：{}，提示符：{}，最后5行内容：\n{}'.format(
                self.total_timeout, int(total_diff_s), self.host, self.prompt, '\n'.join(self.content.strip().split('\n')[-5:])
            )
            print(message)
            is_total_timeout = True
        return is_idle_timeout, is_total_timeout

    def join_buffer(self):
        self.merge_fast_buffer_to_buffer()
        if len(self.buffer_raw) == self.buffer_raw_len:
            # p_debug(self.debug, 'join_buffer 没有收到任何内容')
            self.has_buffer = False
            return None, None
        self.buffer_raw_len = len(self.buffer_raw)
        self.content_raw = b''.join(self.buffer_raw)
        self.content_raw_len = len(self.content_raw)
        self.content = ''.join(self.buffer)
        self.content_len = len(self.content.strip())
        if self.content_len == self.content_len_last:
            p_debug(self.debug, 'join_buffer 没有收到新的实质内容')
            self.content_newin = ''
            self.has_buffer = False
            return None, None
        p_debug(self.debug, 'self.content_len {} self.line_list_len {}'.format(self.content_len, self.line_list_len))
        self.has_buffer = True
        self.idle_tick = ticknow()
        self.content_len_last = self.content_len

        # print_content_summary(content=self.content, count=10)
        self.content_last_50_repr = repr(self.content.strip()[-50:])
        self.content_last_50_repr_reverse = self.content_last_50_repr[::-1]
        # self.last_line = self.content.split("\n")[-1:][0].strip()
        self.last_line = self.line_list_obj.line_list[-1].strip()
        self.last_line_reverse = self.last_line[::-1]
        p_debug(self.debug, '收到内容块数：块数：{}，最后字符：{}，最后行：{}'.format(
            self.content_raw_len, repr(self.content_raw.strip()[-100:]), repr(self.last_line[-120:])))

    def cut(self):
        self.action_pos = len(self.content)
        self.action_line = self.line_list_len

    def parse_action(self):
        if not self.has_buffer:
            return None, None
        if self.action_pos:
            self.content_newin = self.content[self.action_pos:]
        else:
            self.content_newin = self.content
        if self.action_line:
            self.line_list_newin = self.line_list_obj.line_list[self.action_line:]
        else:
            self.line_list_newin = self.line_list_obj.line_list

        if self.line_list_newin:
            last_line_newin = self.line_list_newin[-1].strip()
        else:
            last_line_newin = ''
        p_debug(self.debug, 'self.action_pos {} self.action_line {}'.format(self.action_pos, self.action_line))
        p_debug(self.debug, 'content_newin {}'.format(self.content_newin[-120:]))
        if self.replace_last_doller and last_line_newin.endswith('$'):
            last_line_newin = last_line_newin.strip('$').strip()

        if self.custom_parser:
            state_str, action_str = self.custom_parser(self.content_newin)
            if state_str:
                self.action_pos = self.content_len
                self.action_line = self.line_list_len
                return state_str, action_str

        if self.force_action_dict:
            for except_str, except_action in self.force_action_dict.items():
                if except_str in self.content_newin:
                    state_str = '遇到自定义的强制交互特征串'
                    action_str = except_action
                    self.action_pos = self.action_pos + self.content_newin.find(except_str) + len(except_str)
                    self.action_line = self.line_list_len
                    return state_str, action_str
        if self.except_dict:
            for except_str, except_action in self.except_dict.items():
                if self.content_newin.strip().endswith(except_str):
                    state_str = '最后字符等于自定义的交互特征串'
                    action_str = except_action
                    self.action_pos = self.content_len
                    self.action_line = self.line_list_len
                    return state_str, action_str
        if self.stop_str_list:
            for except_str in self.stop_str_list:
                if except_str in self.content_newin:
                    state_str = '遇到自定义的中断特征串'
                    action_str = None  # 遇到中断特征串，不会执行动作，直接跳出
                    self.action_pos = self.action_pos + self.content_newin.find(except_str) + len(except_str)
                    self.action_line = self.line_list_len
                    return state_str, action_str
        if self.prompt and replace_prompt_char(last_line_newin) == replace_prompt_char(self.prompt):
            state_str = '最后一行等于提示符'
            action_str = ''
            self.action_pos = self.content_len
            self.action_line = self.line_list_len
            return state_str, action_str
        if self.prompt and last_line_newin and replace_prompt_char(self.prompt) in replace_prompt_char(last_line_newin):
            state_str = '最后一行含有提示符'
            action_str = ''
            if replace_prompt_char(self.last_line.replace('\x1b[1D', '').strip()) == replace_prompt_char(self.prompt):
                state_str = '最后一行等于提示符'
            self.action_pos = self.content_len
            self.action_line = self.line_list_len
            return state_str, action_str
        if self.last_line in ['---- More ----', '--More--', '---MORE---']:
            state_str = '最后一行遇到More'
            if self.fast:
                action_str = ' ' * 100
            else:
                action_str = ' '
            self.action_pos = self.content_len
            self.action_line = self.line_list_len
            return state_str, action_str
        if self.prompt_reverse and self.content_last_50_repr_reverse and self.content_last_50_repr_reverse.find(self.prompt_reverse) == 0:
            state_str = '最后字符串遇到提示符'
            action_str = None
            self.action_pos = self.content_len
            self.action_line = self.line_list_len
            return state_str, action_str

        login_except_dict = {
            'The password needs to be changed. Change now? [Y/N]:': 'N\n',
            'day(s). Would you like to input a new one? [Y/N]:': 'N\n'
        }
        for except_str, except_action in login_except_dict.items():
            if self.last_line_reverse.find(except_str[::-1]) == 0:
                state_str = '登录遇到改密码提示'
                action_str = except_action
                self.action_pos = self.content_len
                self.action_line = self.line_list_len
                return state_str, action_str
        quit_except_dict = {
            'Are you sure you want to logout? (y/n): ': 'y\n',
        }
        for except_str, except_action in quit_except_dict.items():
            if self.last_line_reverse.find(except_str[::-1]) == 0:
                state_str = '退出时遇到确认提示'
                action_str = except_action
                self.action_pos = self.content_len
                self.action_line = self.line_list_len
                return state_str, action_str
        return None, None


class SshConnectTest:
    password = None
    key = None
    key_password = None
    use_adminset_key = None
    enable_password = None
    db_prompt = None
    total_line_list = None
    debug = None

    prompt = None
    sysrole = None
    sysname = None
    errorstr = None
    lastout = None
    lastline = None
    action_set = None
    transport = None
    client = None
    channel = None
    sftp = None

    def __init__(self, host, prompt=None, port=None, user=None,
                 password=None, key=None, key_password=None, use_adminset_key=False,
                 enable_password=None, debug=None):
        self.host = host
        if port is None:
            self.port = 22
        else:
            self.port = port
        self.user = user
        self.password = password
        self.key = key
        self.key_password = key_password
        self.use_adminset_key = use_adminset_key
        self.enable_password = enable_password
        self.login_except_dict = {}
        self.db_prompt = prompt
        self.total_line_list = LineList()
        self.debug = debug
        self.option = {}
        self.action_set = set()
        self.client = paramiko.SSHClient()
        self.client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        self.connect()

    def set_option(self, option_key, option_value):
        self.option[option_key] = option_value

    def connect(self):
        try:
            if self.password:
                p_debug(self.debug, 'connect 到主机：{}，使用密码方式'.format(self.host))
                self.client.connect(self.host, self.port, username=self.user, password=self.password,
                                    allow_agent=False, look_for_keys=False, timeout=5)
            elif self.key:
                p_debug(self.debug, 'connect 到主机：{}，使用秘钥方式'.format(self.host))
                self.client.connect(self.host, self.port, username=self.user, key_filename=self.key, password=self.key_password,
                                    allow_agent=False, look_for_keys=False, timeout=5)
            elif self.use_adminset_key:
                key_pass_file = '/var/opt/adminset/data/scripts/update_privilege_monitor/pa.log'
                with open(key_pass_file, 'r') as f:
                    key_pass = f.read().strip()
                key_file = '/root/.ssh/adminset'
                self.user = 'monitor'
                p_debug(self.debug, 'connect 到主机：{}，使用Adminset秘钥'.format(self.host))
                self.client.connect(self.host, self.port, username=self.user, key_filename=key_file, password=key_pass,
                                    allow_agent=False, look_for_keys=False, timeout=5)
            else:
                p_debug(self.debug, '异常，password 和 key 不能同时为空')
                self.errorstr = 'password 和 key 不能同时为空'
                return
            self.transport = self.client.get_transport()
            self.channel = self.client.invoke_shell(term='vt100', width=1024, height=1024)
            self.channel.settimeout(5)
        except socket.timeout:
            p_debug(self.debug, '连接到设备失败，网络不通')
            self.errorstr = '到设备的ssh 端口不通'
            return
        except paramiko.ssh_exception.NoValidConnectionsError as e:
            p_debug(self.debug, '登录到设备ssh 失败：{} 信息：{}'.format(type(e), e))
            p_debug(self.debug, '连接到设备失败，连接异常')
            self.errorstr = '到设备的ssh 连接异常'
            return
        except paramiko.ssh_exception.BadAuthenticationType as e:
            p_debug(self.debug, '登录到设备ssh 失败：{} 信息：{}'.format(type(e), e))
            p_debug(self.debug, '连接到设备失败，验证鉴权方式不匹配')
            self.errorstr = '验证鉴权方式不匹配'
            return
        except paramiko.ssh_exception.AuthenticationException as e:
            p_debug(self.debug, '登录到设备ssh 失败：{} 信息：{}'.format(type(e), e))
            p_debug(self.debug, '连接到设备失败，验证鉴权异常')
            self.errorstr = '验证鉴权异常'
            return
        except Exception as e:
            p_debug(self.debug, '登录到设备ssh 失败：{} 信息：{}'.format(type(e), e))
            p_debug(self.debug, traceback.format_exc())
            self.errorstr = str(e)
            return
        p_debug(self.debug, 'ssh 验证鉴权成功，transport channel 获取成功')
        what_to_do_next_obj = WhatToDoNext(host=self.host, fast=False, debug=self.debug)
        what_to_do_next_obj.set_prompt(prompt=self.db_prompt)
        what_to_do_next_obj.set_total_line_list(total_line_list=self.total_line_list)

        self.get_and_send(what_to_do_next_obj=what_to_do_next_obj, fast=False, prompt=self.db_prompt)

        self.prompt = what_to_do_next_obj.last_line.replace('\x00', '')
        if len(self.prompt) == 0:
            self.prompt = None
        if self.prompt is None:
            if self.debug:
                print_content_summary(self.total_line_list.content(), count=10)
            return

        prompt_split = replace_prompt_char(self.prompt).split('`')
        if len(prompt_split) == 3:
            self.sysrole = prompt_split[0]
            self.sysname = prompt_split[1]
        elif len(prompt_split) == 2:
            self.sysrole = ''
            self.sysname = prompt_split[0]

        p_debug(self.debug, '连接到设备完成，提示符：{}'.format(self.prompt))
        what_to_do_next_obj.set_prompt(self.prompt)
        if self.db_prompt and replace_prompt_char(self.prompt) != replace_prompt_char(self.db_prompt):
            print('设备命令提示符发生改变：{} ==>> {}'.format(repr(self.db_prompt), repr(self.prompt)))

        if self.enable_password is None:
            p_debug(self.debug, 'password_enable 不存在，连接完成，提示符：{}'.format(repr(self.prompt)))
            if self.debug:
                print_content_summary(self.total_line_list.content(), count=10)
            return
        p_debug(self.debug, '存在 password_enable，进入enable模式，发送enable')
        self.channel.send('enable\n')

        except_dict = {
            'Password:': self.enable_password + '\n',
            'password:': self.enable_password + '\n'
        }
        what_to_do_next_obj.set_except_dict(except_dict=except_dict)
        what_to_do_next_obj.cut()
        self.get_and_send(what_to_do_next_obj=what_to_do_next_obj, fast=False, prompt=self.prompt)

        prompt_in_enable = what_to_do_next_obj.last_line.replace('\x00', '')
        if 'password' in prompt_in_enable.lower():
            print('异常，enable再次提示输入密码，似乎enable失败！')
            self.errorstr = 'enable模式进入失败'
            if self.debug:
                print_content_summary(self.total_line_list.content(), count=10)
            return

        if self.prompt == prompt_in_enable:
            if prompt_in_enable[-1] == '#':
                print('注意，enable模式提示符和普通模式一样，末尾为#，断定为无须enable！')
            else:
                print('异常，enable模式提示符和普通模式应该不一样，这可能是enable失败！')
                self.errorstr = 'enable模式进入失败'
                if self.debug:
                    print_content_summary(self.total_line_list.content(), count=10)
                return

        self.prompt = what_to_do_next_obj.last_line.replace('\x00', '')
        p_debug(self.debug, '进入enable 模式完成，连接完成，提示符：{}'.format(self.prompt))
        if self.debug:
            print_content_summary(self.total_line_list.content(), count=10)

    def set_prompt(self, prompt):
        self.prompt = prompt

    def get_transport(self):
        return self.client.get_transport()

    def check_transport(self):
        if self.transport is None:
            return False
        return self.transport.is_active()

    def check_channel(self):
        return not self.channel.exit_status_ready()

    def excute(self, command, stop_only_on_prompt=False, fast=False, except_dict=None, force_action_dict=None, stop_str_list=None, timeout=None):
        if self.errorstr:
            p_debug(self.debug, 'errorstr 不为空，不能执行命令，{}'.format(self.errorstr))
            raise SshException(self.host + ' ' + str(self.prompt) + ' ' + str(self.errorstr))
        if not self.check_transport():
            p_debug(self.debug, 'transport_isalive 检查发现：{} {} 通道不在存活状态'.format(self.host, self.prompt))
            raise SshException('{} {} 通道不在存活状态'.format(self.host, self.prompt))
        p_debug(self.debug, '\n')
        p_debug(self.debug, '准备执行命令：{}'.format(repr(command)))
        p_debug(self.debug, '是否fast模式：{}'.format(fast))

        if len(command + '\n') > 2040:
            p_debug(self.debug, '命令太长，将被拆分成最大2040长度的列表，逐段send')
            part = 0
            command = command + '\n'
            while True:
                start = part * 2040
                end = start + 2040
                part += 1
                command_part = command[start:end]
                if not command_part:
                    break
                p_debug(self.debug, '{}\n{}'.format(
                    '-' * 100, '分段长度：{}'.format(len(command_part))
                ))
                if self.debug:
                    print_content_summary(command_part)
                self.channel.send(command_part)
                sleep(0.1)
        else:
            self.channel.send(command + '\n')
        p_debug(self.debug, '命令已发出')
        sleep(0.5)
        what_to_do_next_obj = WhatToDoNext(host=self.host, fast=fast, debug=self.debug)
        what_to_do_next_obj.set_prompt(prompt=self.prompt)
        what_to_do_next_obj.set_total_line_list(total_line_list=self.total_line_list)
        what_to_do_next_obj.set_except_dict(except_dict=except_dict)
        what_to_do_next_obj.set_force_action_dict(force_action_dict=force_action_dict)
        what_to_do_next_obj.set_stop_str_list(stop_str_list=stop_str_list)
        if timeout:
            what_to_do_next_obj.idle_timeout = timeout
            what_to_do_next_obj.total_timeout = 2*timeout
        p_debug(self.debug, 'WhatToDoNext 初始化完成，启动')
        recv_str = self.get_and_send(what_to_do_next_obj=what_to_do_next_obj,
                                     fast=fast, prompt=self.prompt, stop_only_on_prompt=stop_only_on_prompt)
        return recv_str

    def get_and_send(self, what_to_do_next_obj, fast=False, prompt=None, stop_only_on_prompt=False):
        p_debug(self.debug, '\n')
        p_debug(self.debug, '{}\n{}\n{}\n{}\n{}\n{}\n'.format(
            '{} get_and_send start {}'.format('-' * 50, '-' * 50),
            'fast：{}'.format(fast),
            'prompt：{}'.format(self.prompt),
            'stop_only_on_prompt：{}'.format(stop_only_on_prompt),
            'except_dict：{}'.format(what_to_do_next_obj.except_dict),
            'stop_str_list：{}'.format(what_to_do_next_obj.stop_str_list),
            'idle_timeout：{}'.format(what_to_do_next_obj.idle_timeout),
            'total_timeout：{}'.format(what_to_do_next_obj.total_timeout),
        ))
        timer = Timer()
        if not self.check_transport():
            p_debug(self.debug, 'transport_isalive 检查发现：{} {} 通道不在存活状态'.format(self.host, self.prompt))
            raise SshException('{} {} 通道不在存活状态'.format(self.host, self.prompt))
        if prompt is None:
            prompt = self.prompt
        if fast:
            recv_len = 10240
            self.channel.settimeout(0.5)
            self.channel.send(' ' * 5)
        else:
            recv_len = 4096
            self.channel.settimeout(5)
        while True:
            start = ticknow()
            try:
                sleep(0.05)
                if fast:
                    try:
                        while True:
                            what_to_do_next_obj.fast_append(self.channel.recv(recv_len))
                    except socket.timeout:
                        pass
                else:
                    what_to_do_next_obj.append(self.channel.recv(recv_len))
                # p_debug(self.debug, 'channel.recv out')
                what_to_do_next_obj.join_buffer()
                state_str, action_str = what_to_do_next_obj.parse_action()

                if self.debug and state_str and not (fast and '遇到More' in state_str):
                    print('分析出状态：' + state_str + '，动作：' + repr(action_str))
                if isfunction(action_str):
                    p_debug(self.debug, '执行回调函数：{}'.format(action_str.__name__))
                    action_str(what_to_do_next_obj.content)
                    what_to_do_next_obj.idle_tick = ticknow()
                    what_to_do_next_obj.total_tick = ticknow()
                    p_debug(self.debug, '执行回调函数后，继续接收')
                    continue
                if action_str:
                    self.channel.send(action_str)
                if state_str and '提示符' in state_str:
                    break
                if state_str and '遇到自定义的中断特征串' in state_str:
                    break
                if state_str and not action_str:
                    break
            except socket.timeout:
                if stop_only_on_prompt or what_to_do_next_obj.stop_str_list:
                    p_debug(self.debug, '慢速模式接收超时，需等待提示符或自定义中断字符')
                else:
                    p_debug(self.debug, '慢速模式接收超时，未设置强制等待提示符或自定义中断字符，将中断接收')
                    break
            except Exception as e:
                print('SshConnect get_and_send 发生未知异常：{}，信息：{}'.format(type(e), e))
                print(traceback.format_exc())
                self.errorstr = str(e)
                raise SshException('主机：{}，提示符：{}，发生未知异常：{}'.format(self.host, prompt, e))
            if not self.check_channel():
                p_debug(self.debug, 'ssh终端不在存活状态')
                break

            is_idle_timeout, is_total_timeout = what_to_do_next_obj.check_idle()
            if is_idle_timeout or is_total_timeout:
                except_str = '{} ssh运行超时，idle：{}，total：{}'.format(self.host, is_idle_timeout, is_total_timeout)
                ssh_debug = self.total_line_list.content()
                raise SshException(except_str=except_str, ssh_debug=ssh_debug)
            p_debug(self.debug, '{} get_and_send while 结束，耗时：{}，执行耗时：{}'.format('~' * 80, round(ticknow() - start, 2), timer.str()))
        if self.debug:
            # print_content_summary(what_to_do_next_obj.content, count=10)
            print_content_summary(self.total_line_list.content(start=what_to_do_next_obj.total_line_list_start), count=10)

        self.lastout = '\n'.join(what_to_do_next_obj.content.split('\n')[-5:])
        self.lastline = '\n'.join(what_to_do_next_obj.content.split('\n')[-1:])
        return self.total_line_list.content(start=what_to_do_next_obj.total_line_list_start)

    def open_sftp(self):
        if self.sftp is None:
            p_debug(self.debug, '创建sfpt client')
            try:
                self.sftp = self.transport.open_sftp_client()
            except paramiko.ssh_exception.SSHException as e:
                except_str = 'sftp打开异常：{}'.format(e)
                ssh_debug = self.total_line_list.content()
                raise SshException(except_str=except_str, ssh_debug=ssh_debug)

    def put_file_content(self, content, remote_file):
        self.open_sftp()
        p_debug(self.debug, '上传内容到文件，源：{}字符，目标：{}'.format(len(content), remote_file))
        put_status = self.sftp.putfo(StringIO(content), remote_file)
        return put_status

    def get_file_content(self, remote_file):
        if self.sftp is None:
            p_debug(self.debug, '创建sfpt client')
            self.sftp = self.transport.open_sftp_client()
        p_debug(self.debug, '下载文件，源：{}'.format(remote_file))
        content = StringIO()
        put_status = self.sftp.getfo(remote_file, content)
        return content, put_status

    def putfile(self, local_file, remote_file):
        if self.sftp is None:
            p_debug(self.debug, '创建sfpt client')
            self.sftp = self.transport.open_sftp_client()
        p_debug(self.debug, '上传文件，源：{}，目标：{}'.format(local_file, remote_file))
        put_status = self.sftp.put(local_file, remote_file)
        return put_status

    def getfile(self, remote_file, local_file):
        if self.sftp is None:
            p_debug(self.debug, '创建sfpt client')
            self.sftp = self.transport.open_sftp_client()
        p_debug(self.debug, '下载文件，源：{}，目标：{}'.format(remote_file, local_file))
        self.sftp.get(remote_file, local_file)

    def removefile(self, remote_file):
        if self.sftp is None:
            p_debug(self.debug, '创建sfpt client')
            self.sftp = self.transport.open_sftp_client()
        p_debug(self.debug, '删除文件，目标：{}'.format(remote_file))
        self.sftp.remove(remote_file)

    def createpath(self,path):
        if self.sftp is None:
            p_debug(self.debug, '创建sfpt client')
            self.sftp = self.transport.open_sftp_client()
        try:
            self.sftp.stat(path)
        except FileNotFoundError:
            self.sftp.mkdir(path)

    def clear(self):
        if self.channel:
            self.channel.close()
        if self.sftp:
            self.sftp.close()
        if self.transport:
            self.transport.close()
        if self.client:
            self.client.close()
        self.channel = None
        self.sftp = None
        self.transport = None
        self.client = None
